//
//  SLMP4Parser.m
//  SLVideoParser
//
//  Created by S_LINE on 2020/6/19.
//  Copyright © 2020 S_LINE. All rights reserved.
//

#import "SLMP4Parser.h"

#import "SLParserUtil.h"
#import "SLMP4Parser+Root.h"
#import "SLMP4Parser+Moov.h"
#import "SLMP4Parser+Trak.h"
#import "SLMP4Parser+Mdia.h"
#import "SLMP4Parser+Minf.h"
#import "SLMP4Parser+Dinf.h"
#import "SLMP4Parser+Stbl.h"
#import "SLMP4Parser+Udta.h"

@interface SLMP4Parser ()

@property (nonatomic, copy) NSString *path;

@property (nonatomic, assign) uint64_t timeScale;

@end

static const int boxSizeByteLength = 4;
static const int boxTypeByteLength = 4;

@implementation SLMP4Parser

- (instancetype)initWithPath:(NSString *)path
{
    self = [super init];
    if (self)
    {
        self.path = path;
        [self readFile];
    }
    return self;
}

- (void)readFile
{
    if ([self.path isKindOfClass:[NSString class]] && self.path.length > 0)
    {
        NSFileManager *fileManager = [NSFileManager defaultManager];
        if ([fileManager fileExistsAtPath:self.path])
        {
            self.fileData = [NSData dataWithContentsOfFile:self.path];
        }
    }
}

- (NSArray<SLMP4Box *> *)boxParser:(NSData *)data
{
    NSMutableArray *boxes = [NSMutableArray new];
    int64_t offset = 0;
    
    while (offset < data.length)
    {
        SLMP4Box *box = [[SLMP4Box alloc] init];
        box.offset = offset;
        if (offset + boxSizeByteLength + boxTypeByteLength > data.length)
        {
            break;
        }
        NSData *sizeData = [data subdataWithRange:NSMakeRange(offset, boxSizeByteLength)];
        NSData *typeData = [data subdataWithRange:NSMakeRange(offset + boxSizeByteLength, boxTypeByteLength)];
        uint64_t boxSize = [SLParserUtil hexToDecimalWithData:sizeData];
        NSString *typeString = [SLParserUtil hexToStringTypeWithData:typeData];
        if (offset + boxSize > data.length)
        {
            break;
        }
        NSData *body = [data subdataWithRange:NSMakeRange(offset + boxSizeByteLength + boxTypeByteLength, boxSize - boxSizeByteLength - boxTypeByteLength)];
        box.header.size = boxSize;
        box.header.typeDesc = typeString;
        offset = offset + boxSize;
        [boxes addObject:box];
        
        switch (box.header.type)
        {
            case SLMP4BoxType_ftyp:
            {
                NSDictionary *dict = [self ftypParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_moov:
            case SLMP4BoxType_trak:
            case SLMP4BoxType_mdia:
            case SLMP4BoxType_minf:
            case SLMP4BoxType_stbl:
            case SLMP4BoxType_udta:
            case SLMP4BoxType_edts:
            {
                NSArray<SLMP4Box *> *boxes = [self boxParser:body];
                box.boxes = boxes;
                break;
            }
            case SLMP4BoxType_mvhd:
            {
                NSDictionary *dict = [self mvhdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_tkhd:
            {
                NSDictionary *dict = [self tkhdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_mdhd:
            {
                NSDictionary *dict = [self mdhdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_hdlr:
            {
                NSDictionary *dict = [self hdlrParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_vmhd:
            {
                NSDictionary *dict = [self vmhdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_smhd:
            {
                NSDictionary *dict = [self smhdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_dref:
            {
                NSDictionary *dict = [self drefParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stsd:
            {
                NSDictionary *dict = [self stsdParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stts:
            {
                NSDictionary *dict = [self sttsParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stss:
            {
                NSDictionary *dict = [self stssParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stsc:
            {
                NSDictionary *dict = [self stscParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stsz:
            {
                NSDictionary *dict = [self stszParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_stco:
            {
                NSDictionary *dict = [self stcoParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_meta:
            {
                NSDictionary *dict = [self metaParser:body];
                box.dicValue = dict;
                break;
            }
            case SLMP4BoxType_elst:
            {
                NSDictionary *dict = [self elstParser:body];
                box.dicValue = dict;
                break;
            }
            default:
            {
                box.body = body;
                break;
            }
        }
    }
    
    return [boxes copy];
}

- (SLMP4Box *)getMP4BoxWithBoxType:(SLMP4BoxType)boxType fromMP4Boxes:(NSArray *)mp4Boxes
{
    if (![mp4Boxes isKindOfClass:[NSArray class]] || mp4Boxes.count == 0)
    {
        return nil;
    }
    __block SLMP4Box *targetBox;
    for (int i = 0; i < mp4Boxes.count; i++)
    {
        SLMP4Box *mp4Box = [mp4Boxes objectAtIndex:i];
        if ([mp4Box isKindOfClass:[SLMP4Box class]])
        {
            if (mp4Box.header.type == boxType)
            {
                targetBox = mp4Box;
                break;
            }
            else if ([mp4Box.boxes isKindOfClass:[NSArray class]] && mp4Box.boxes.count > 0)
            {
                targetBox = [self getMP4BoxWithBoxType:boxType fromMP4Boxes:mp4Box.boxes];
                if ([targetBox isKindOfClass:[SLMP4Box class]] && targetBox.header.type == boxType)
                {
                    break;
                }
            }
        }
    }
    return targetBox;
}

- (uint64_t)parseLocationWithSeekTime:(uint64_t)seekTime
{
    SLMP4Box *mdhdBox = [self getMP4BoxWithBoxType:SLMP4BoxType_mdhd fromMP4Boxes:self.MP4Boxes];
    uint64_t timeScale = 0;
    if ([[mdhdBox.dicValue objectForKey:@"timescale"] isKindOfClass:[NSNumber class]])
    {
        timeScale = [[mdhdBox.dicValue objectForKey:@"timescale"] unsignedLongLongValue];
    }
    uint64_t realSeekTime = seekTime * timeScale;
    uint64_t seekTimeLocation = 0;
    SLMP4Box *sttsBox = [self getMP4BoxWithBoxType:SLMP4BoxType_stts fromMP4Boxes:self.MP4Boxes];
    NSArray *sttsKeys = [sttsBox.dicValue allKeys];
    NSArray *sttsEntryInfo = nil;
    uint64_t sampleIndex = 0;
    if ([sttsKeys containsObject:@"entryInfo"])
    {
        sttsEntryInfo = [sttsBox.dicValue objectForKey:@"entryInfo"];
    }
    if ([sttsEntryInfo isKindOfClass:[NSArray class]] && sttsEntryInfo.count > 0)
    {
        uint64_t totalTime = 0;
        for (int i = 0; i < sttsEntryInfo.count; i++)
        {
            NSDictionary *entryDict = [sttsEntryInfo objectAtIndex:i];
            uint64_t sampleCount = 0;
            if ([[entryDict objectForKey:@"sampleCount"] isKindOfClass:[NSNumber class]])
            {
                sampleCount = [[entryDict objectForKey:@"sampleCount"] unsignedLongLongValue];
            }
            uint64_t sampleDuration = 0;
            if ([[entryDict objectForKey:@"sampleDuration"] isKindOfClass:[NSNumber class]])
            {
                sampleDuration = [[entryDict objectForKey:@"sampleDuration"] unsignedLongLongValue];
            }
            totalTime += sampleCount * sampleDuration;
            sampleIndex += sampleCount;
            if (realSeekTime <= totalTime)
            {
                if (i == 0)
                {
                    sampleIndex = (int64_t)(realSeekTime / sampleDuration) + 1;
                }
                else
                {
                    int64_t timeOffset =  (int64_t)(realSeekTime - (totalTime - (sampleCount * sampleDuration)));
                    int64_t index = timeOffset / sampleDuration;
                    sampleIndex = sampleIndex - sampleCount + index + 1;
                }
                break;
            }
        }
    }
    
    SLMP4Box *stscBox = [self getMP4BoxWithBoxType:SLMP4BoxType_stsc fromMP4Boxes:self.MP4Boxes];
    NSArray *stscKeys = [stscBox.dicValue allKeys];
    uint64_t stscEntryCount = 0;
    if ([stscKeys containsObject:@"entryCount"] && [[stscBox.dicValue objectForKey:@"entryCount"] isKindOfClass:[NSNumber class]])
    {
        stscEntryCount = [[stscBox.dicValue objectForKey:@"entryCount"] unsignedLongLongValue];
    }
    NSArray *stscEntryInfo = nil;
    if ([stscKeys containsObject:@"entryInfo"])
    {
        stscEntryInfo = [stscBox.dicValue objectForKey:@"entryInfo"];
    }
    uint64_t chunkIndex = 0;
    uint64_t samplesOffset = 0;
    if (stscEntryCount == 1)
    {
        NSDictionary *dict = [stscEntryInfo firstObject];
        uint64_t samplesPerChunk = 0;
        if ([[dict objectForKey:@"samplesPerChunk"] isKindOfClass:[NSNumber class]])
        {
            samplesPerChunk = [[dict objectForKey:@"samplesPerChunk"] unsignedLongLongValue];
        }
        uint64_t firstChunk = -1;
        if ([[dict objectForKey:@"firstChunk"] isKindOfClass:[NSNumber class]]) {
            firstChunk = [[dict objectForKey:@"firstChunk"] unsignedLongLongValue];
        }
        if (sampleIndex % samplesPerChunk == 0)
        {
            chunkIndex = firstChunk + sampleIndex / samplesPerChunk - 1;
            if (sampleIndex != 0)
            {
                samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:(samplesPerChunk - 1)];
            }
        }
        else
        {
            chunkIndex = firstChunk + sampleIndex / samplesPerChunk;
            samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:((sampleIndex % samplesPerChunk) - 1)];
        }
    }
    else if (stscEntryCount > 1)
    {
        uint64_t totalSamplesCount = 0;
        for (int i = 0; i < stscEntryInfo.count - 1; i++)
        {
            NSDictionary *currentChunkDict = [stscEntryInfo objectAtIndex:i];
            uint64_t currentFirstChunk = 0;
            if ([[currentChunkDict objectForKey:@"firstChunk"] isKindOfClass:[NSNumber class]])
            {
                currentFirstChunk = [[currentChunkDict objectForKey:@"firstChunk"] unsignedLongLongValue];
            }
            uint64_t currentSamplesPerChunk = 0;
            if ([[currentChunkDict objectForKey:@"samplesPerChunk"] isKindOfClass:[NSNumber class]])
            {
                currentSamplesPerChunk = [[currentChunkDict objectForKey:@"samplesPerChunk"] unsignedLongLongValue];
            }
            NSDictionary *nextChunkDict = [stscEntryInfo objectAtIndex:(i + 1)];
            uint64_t nextFirstChunk = 0;
            if ([[nextChunkDict objectForKey:@"firstChunk"] isKindOfClass:[NSNumber class]]) {
                nextFirstChunk = [[nextChunkDict objectForKey:@"firstChunk"] unsignedLongLongValue];
            }
            uint64_t nextSamplesPerChunk = 0;
            if ([[nextChunkDict objectForKey:@"samplesPerChunk"] isKindOfClass:[NSNumber class]])
            {
                nextSamplesPerChunk = [[nextChunkDict objectForKey:@"samplesPerChunk"] unsignedLongLongValue];
            }
            if (currentFirstChunk > 0 && currentSamplesPerChunk > 0 && nextFirstChunk > 0 && nextSamplesPerChunk > 0)
            {
                totalSamplesCount += (nextFirstChunk - currentFirstChunk) * currentSamplesPerChunk;
                if (sampleIndex <= totalSamplesCount)
                {
                    uint64_t sampleOffsetIndex = sampleIndex - (totalSamplesCount - ((nextFirstChunk - currentFirstChunk) * currentSamplesPerChunk));
                    if (sampleOffsetIndex % currentSamplesPerChunk == 0)
                    {
                        chunkIndex = currentFirstChunk + ((sampleOffsetIndex / currentSamplesPerChunk) - 1);
                        if (sampleOffsetIndex != 0)
                        {
                            samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:(currentSamplesPerChunk - 1)];
                        }
                    }
                    else
                    {
                        chunkIndex = currentFirstChunk + (sampleOffsetIndex / currentSamplesPerChunk);
                        samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:((sampleOffsetIndex % currentSamplesPerChunk) - 1)];
                    }
                    break;
                }
                else if (i == (stscEntryInfo.count - 2))
                {
                    uint64_t sampleOffsetIndex = sampleIndex - totalSamplesCount;
                    if (sampleOffsetIndex % currentSamplesPerChunk == 0)
                    {
                        chunkIndex = currentFirstChunk + ((sampleOffsetIndex / nextSamplesPerChunk) - 1);
                        if (sampleOffsetIndex != 0)
                        {
                            samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:(nextSamplesPerChunk - 1)];
                        }
                    }
                    else
                    {
                        chunkIndex = currentFirstChunk + (sampleOffsetIndex / nextSamplesPerChunk);
                        samplesOffset = [self getSamplesOffsetWithEndSampleIndex:sampleIndex beforeCount:((sampleOffsetIndex % nextSamplesPerChunk) - 1)];
                    }
                    break;
                }
            }
        }
    }
    uint64_t chunkOffset = 0;
    if (chunkIndex > 0)
    {
        chunkOffset = [self getChunkOffsetWithChunkIndex:chunkIndex];
    }
    if (chunkOffset > 0)
    {
        seekTimeLocation = chunkOffset + samplesOffset;
    }
    return seekTimeLocation;
}

- (uint64_t)getChunkOffsetWithChunkIndex:(uint64_t)chunkIndex
{
    uint64_t chunkOffset = 0;
    uint64_t realChunkIndex = chunkIndex - 1;
    SLMP4Box *stcoBox = [self getMP4BoxWithBoxType:SLMP4BoxType_stco fromMP4Boxes:self.MP4Boxes];
    NSArray *stcoEntryInfo = nil;
    if ([[stcoBox.dicValue objectForKey:@"entryInfo"] isKindOfClass:[NSArray class]])
    {
        stcoEntryInfo = [stcoBox.dicValue objectForKey:@"entryInfo"];
    }
    if ([stcoEntryInfo isKindOfClass:[NSArray class]] && realChunkIndex < stcoEntryInfo.count)
    {
        NSDictionary *dict = [stcoEntryInfo objectAtIndex:realChunkIndex];
        if ([[dict objectForKey:@"chunkOffset"] isKindOfClass:[NSNumber class]])
        {
            chunkOffset = [[dict objectForKey:@"chunkOffset"] unsignedLongLongValue];
        }
    }
    return chunkOffset;
}

- (uint64_t)getSamplesOffsetWithEndSampleIndex:(uint64_t)endSampleIndex beforeCount:(uint64_t)beforeCount
{
    uint64_t samplesOffset = 0;
    
    SLMP4Box *stszBox = [self getMP4BoxWithBoxType:SLMP4BoxType_stsz fromMP4Boxes:self.MP4Boxes];
    if ([[stszBox.dicValue objectForKey:@"entryInfo"] isKindOfClass:[NSArray class]])
    {
        NSArray *entryInfo = [stszBox.dicValue objectForKey:@"entryInfo"];
        for (uint64_t i = (endSampleIndex - beforeCount); i < endSampleIndex; i++)
        {
            uint64_t realIndex = i - 1;
            if (realIndex < entryInfo.count)
            {
                NSDictionary *dict = [entryInfo objectAtIndex:realIndex];
                if ([[dict objectForKey:@"sampleSubSize"] isKindOfClass:[NSNumber class]])
                {
                    samplesOffset += [[dict objectForKey:@"sampleSubSize"] unsignedLongLongValue];
                }
            }
        }
    }
    
    return samplesOffset;
}

- (void)test
{
    NSLog(@"------info------ start");
    NSArray *boxes = [self boxParser:self.fileData];
    self.MP4Boxes = boxes;
    NSLog(@"------info------ end");
}

#pragma mark - setter
- (void)setFileData:(NSData *)fileData
{
    _fileData = fileData;
    
    NSArray *boxes = [self boxParser:self.fileData];
    self.MP4Boxes = boxes;
}

@end
